﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Autofac;
using Cloak.Globalization;
using Cloak.Linq;

namespace Cloak.Mvp.Autofac
{
	/// <summary>
	/// Extension methods for registering presenters associated with view types
	/// </summary>
	public static class PresenterRegistrationExtensions
	{
		/// <summary>
		/// Registers the presenters associated with views in the specified asssemblies
		/// </summary>
		/// <param name="builder">The <see cref="ContainerBuilder"/> where registrations are made</param>
		/// <param name="viewPresenterIndex">The mapping between view types and presenter types</param>
		/// <param name="viewAssemblies">The assemblies containing view types</param>
		public static void RegisterPresenters(
			this ContainerBuilder builder,
			IViewPresenterIndex viewPresenterIndex,
			IEnumerable<Assembly> viewAssemblies)
		{
			Contract.Requires(builder != null);
			Contract.Requires(viewPresenterIndex != null);
			Contract.Requires(viewAssemblies != null);

			var viewAssemblyTypes = viewAssemblies.SelectMany(viewAssembly => viewAssembly.GetTypes());

			builder.RegisterPresenters(viewPresenterIndex, viewAssemblyTypes);
		}

		/// <summary>
		/// Registers the presenters associated with the specified view types
		/// </summary>
		/// <param name="builder">The <see cref="ContainerBuilder"/> where registrations are made</param>
		/// <param name="viewPresenterIndex">The mapping between view types and presenter types</param>
		/// <param name="viewTypes">The types whose associated presenters are registered</param>
		public static void RegisterPresenters(
			this ContainerBuilder builder,
			IViewPresenterIndex viewPresenterIndex,
			IEnumerable<Type> viewTypes)
		{
			Contract.Requires(builder != null);
			Contract.Requires(viewPresenterIndex != null);
			Contract.Requires(viewTypes != null);

			foreach(var viewType in viewTypes)
			{
				foreach(var presenter in viewPresenterIndex.GetPresenters(viewType))
				{
					RegisterPresenter(builder, presenter);
				}
			}
		}

		private static void RegisterPresenter(ContainerBuilder builder, IPresenterReference presenter)
		{
			var presenterType = presenter.GetType();

			if(presenterType == typeof(PresenterReference))
			{
				RegisterBasicPresenter(builder, (PresenterReference) presenter);
			}
			else if(presenterType == typeof(NamedPresenterReference))
			{
				RegisterNamedPresenter(builder, (NamedPresenterReference) presenter);
			}
			else if(presenterType == typeof(KeyedPresenterReference))
			{
				RegisterKeyedPresenter(builder, (KeyedPresenterReference) presenter);
			}
			else
			{
				throw new MvpException(Resources.PresenterReferenceTypeNotSupported.FormatInvariant(presenterType));
			}
		}

		private static void RegisterBasicPresenter(ContainerBuilder builder, PresenterReference presenter)
		{
			// () => RegisterBasicPresenter<T>(builder)

			RegisterPresenter(presenter.Type, "RegisterBasicPresenter", builder);
		}

		private static void RegisterNamedPresenter(ContainerBuilder builder, NamedPresenterReference presenter)
		{
			// () => RegisterNamedPresenter<T>(builder, presenter.Name)

			RegisterPresenter(presenter.Type, "RegisterNamedPresenter", builder, presenter.Name);
		}

		private static void RegisterKeyedPresenter(ContainerBuilder builder, KeyedPresenterReference presenter)
		{
			// () => RegisterKeyedPresenter<T>(builder, presenter.Key)

			RegisterPresenter(presenter.Type, "RegisterKeyedPresenter", builder, presenter.Key);
		}

		private static void RegisterPresenter(Type presenterType, string methodName, params object[] arguments)
		{
			var registerCall = Expression.Call(
				typeof(PresenterRegistrationExtensions),
				methodName,
				new[] { presenterType },
				arguments.ToConstants().ToArray());

			// TODO: Ensure compiling the actions works as expected

			var registerAction = Expression.Lambda<Action>(registerCall).Compile();

			registerAction();
		}

		private static void RegisterBasicPresenter<T>(ContainerBuilder builder)
		{
			builder.RegisterType<T>().InstancePerDependency();
		}

		private static void RegisterNamedPresenter<T>(ContainerBuilder builder, string name)
		{
			builder.RegisterType<T>().Named<T>(name).InstancePerDependency();
		}

		private static void RegisterKeyedPresenter<T>(ContainerBuilder builder, object key)
		{
			builder.RegisterType<T>().Keyed<T>(key).InstancePerDependency();
		}
	}
}